///////////////////////////////////////////////////////////////////////////////
//
//  This file is part of MathLib.NET.
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//  
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//  
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library;  If not, see 
//  <http://www.gnu.org/licenses/>.
//
///////////////////////////////////////////////////////////////////////////////

using ILNumerics;
using ILNumerics.BuiltInFunctions;
using System.Collections.Generic;
using System.Collections;

// Array Manipulation functions
// ===========================
//
// blkdiag - Construct block diagonal matrix from input arguments 
// cat - Concatenate arrays along specified dimension 
// circshift - Shift array circularly 
// diag - Diagonal matrices and diagonals of matrix 
// flipdim - Flip array along specified dimension 
// fliplr - Flip matrix left to right 
// flipud - Flip matrix up to down 
// horzcat - Concatenate arrays horizontally 
// ipermute - Inverse permute dimensions of N-D array 
// permute - Rearrange dimensions of N-D array 
// repmat - Replicate and tile array 
// reshape - Reshape array 
// rot90 - Rotate matrix 90 degrees 
// shiftdim - Shift dimensions 
// sort - Sort array elements in ascending or descending order 
// sortrows - Sort rows in ascending order 
// squeeze - Remove singleton dimensions 
// vectorize - Vectorize expression 
// vertcat - Concatenate arrays vertically 

namespace MathLib
{
    /// <summary>
    /// Array manipulation functions.
    /// </summary>
    public partial class MLMath
    {
        /// <summary>
        /// Copy supplied matrices into single matrix as diagonal blocks.
        /// </summary>
        /// <typeparam name="T">Matrix type</typeparam>
        /// <param name="matrices">Matrices to copy</param>
        /// <returns>Single block diagonal matrix</returns>
        public static ILBaseArray blkdiag(params ILBaseArray[] matrices)
        {
            return blkdiag((IEnumerable)matrices);
        }

        /// <summary>
        /// Copy supplied matrices into single matrix as diagonal blocks.
        /// </summary>
        /// <typeparam name="T">Matrix type</typeparam>
        /// <param name="matrices">Matrices to copy</param>
        /// <returns>Single block diagonal matrix</returns>
        public static ILBaseArray blkdiag(IEnumerable matrices)
        {
            int cols = 0;
            int rows = 0;

            bool complexOutput = false;

            // Calculate dimensions of output matrix
            foreach (ILBaseArray mat in matrices)
            {
                int matcols = 1;
                int matrows = 1;
                foreach (int dim in mat.Dimensions.ToIntArray())
                {
                    if (dim > 1)
                    {
                        if (matcols == 1)
                            matcols = dim;
                        else
                            matrows = dim;
                    }
                }

                cols += matcols;
                rows += matrows;

                if (mat.IsComplex) complexOutput = true;
            }

            if (complexOutput)
            {
                // Construct output matrix
                ILArray<complex> outMat = new ILArray<complex>(new int[] { cols, rows });

                // Fill output matrix
                int col = 0;
                int row = 0;
                foreach (ILBaseArray mat in matrices)
                {
                    int matcols = 1;
                    int matrows = 1;
                    foreach (int dim in mat.Dimensions.ToIntArray())
                    {
                        if (dim > 1)
                        {
                            if (matcols == 1)
                                matcols = dim;
                            else
                                matrows = dim;
                        }
                    }

                    if (mat is ILArray<complex>)
                    {
                        ILArray<complex> matc = mat as ILArray<complex>;
                        for (int c = 0; c < matcols; c++)
                        {
                            for (int r = 0; r < matrows; r++)
                            {
                                outMat.InternalArray4Experts[(r + row) * cols + c + col] = matc.InternalArray4Experts[r * matcols + c];
                            }
                        }
                    }
                    else
                    {
                        ILArray<double> matd = mat as ILArray<double>;
                        for (int c = 0; c < matcols; c++)
                        {
                            for (int r = 0; r < matrows; r++)
                            {
                                outMat.InternalArray4Experts[(r + row) * cols + c + col] = matd.InternalArray4Experts[r * matcols + c];
                            }
                        }
                    }

                    col += matcols;
                    row += matrows;
                }

                return outMat;
            }
            else
            {
                // Construct output matrix
                ILArray<double> outMat = new ILArray<double>(new int[] { cols, rows });

                // Fill output matrix
                int col = 0;
                int row = 0;
                foreach (ILArray<double> mat in matrices)
                {
                    int matcols = 1;
                    int matrows = 1;
                    foreach (int dim in mat.Dimensions.ToIntArray())
                    {
                        if (dim > 1)
                        {
                            if (matcols == 1)
                                matcols = dim;
                            else
                                matrows = dim;
                        }
                    }

                    for (int c = 0; c < matcols; c++)
                    {
                        for (int r = 0; r < matrows; r++)
                        {
                            outMat.InternalArray4Experts[(r + row) * cols + c + col] = mat.InternalArray4Experts[r * matcols + c];
                        }
                    }

                    col += matcols;
                    row += matrows;
                }

                return outMat;
            }
        }

        /// <summary>
        /// Concatenates a set of real arrays along the specified dimension.
        /// </summary>
        /// <param name="dim">Dimension to concatenate along</param>
        /// <param name="arrays">Real arrays to concatenate</param>
        /// <returns>Single concatenated array</returns>
        public static ILBaseArray cat(int dim, params ILBaseArray[] arrays)
        {
            return cat(dim, (IEnumerable)arrays);
        }

        /// <summary>
        /// Concatenates a set of real arrays along the specified dimension.
        /// </summary>
        /// <param name="dim">Dimension to concatenate along</param>
        /// <param name="arrays">Real arrays to concatenate</param>
        /// <returns>Single concatenated array</returns>
        public static ILBaseArray cat(int dim, IEnumerable arrays)
        {
            bool complexOutput = false;
            ILBaseArray firstArray = null;
            foreach (ILBaseArray array in arrays)
            {
                if (firstArray == null)
                    firstArray = array;
                if (array.IsComplex)
                {
                    complexOutput = true;
                    break;
                }
            }

            if (firstArray == null) throw new MathException("cat() requires at least one array.");

            if (complexOutput)
            {
                ILArray<complex> output = null;
                foreach (ILBaseArray array in arrays)
                {
                    if (output == null)
                    {
                        if (array is ILArray<complex>)
                            output = array as ILArray<complex>;
                        else
                            output = ILMath.tocomplex(array);
                    }
                    else
                    {
                        if (array is ILArray<complex>)
                            output = output.Concat(array as ILArray<complex>, dim);
                        else
                            output = output.Concat(ILMath.tocomplex(array), dim);
                    }
                }
                return output;
            }
            else
            {
                if (firstArray is ILArray<double>)
                {
                    ILArray<double> output = null;
                    foreach (ILBaseArray array in arrays)
                    {
                        if (output == null)
                        {
                            output = array as ILArray<double>;
                        }
                        else
                        {
                            if (output.IsEmpty) return array;
                            if (array.IsEmpty) return output;
                            output = output.Concat(array as ILArray<double>, dim);
                        }
                    }
                    return output;
                }
                else if (firstArray is ILArray<float>)
                {
                    ILArray<float> output = null;
                    foreach (ILBaseArray array in arrays)
                    {
                        if (output == null)
                        {
                            output = array as ILArray<float>;
                        }
                        else
                        {
                            if (output.IsEmpty) return array;
                            if (array.IsEmpty) return output;
                            output = output.Concat(array as ILArray<float>, dim);
                        }
                    }
                    return output;
                }
                else
                    return null;
            }            
        }

        /// <summary>
        /// Replicate and tile matrix.
        /// </summary>
        /// <typeparam name="T">Matrix element type</typeparam>
        /// <param name="array">Array to replicate</param>
        /// <param name="dims">Dimensions of output array</param>
        /// <returns>Replicated and tiled output array</returns>
        public static ILArray<T> repmat<T>(ILArray<T> array, params int[] dims)
        {
            return ILMath.repmat<T>(array, dims);
        }

        /// <summary>
        /// Replicate and tile matrix.
        /// </summary>
        /// <typeparam name="T">Matrix element type</typeparam>
        /// <param name="array">Array to replicate</param>
        /// <param name="dims">Dimensions of output array</param>
        /// <returns>Replicated and tiled output array</returns>
        public static ILArray<T> repmat<T>(ILArray<T> array, IronPython.Runtime.List dims)
        {
            int[] intDims = new int[dims.Count];
            for (int i = 0; i < intDims.Length; i++) intDims[i] = (int)dims[i];
            return ILMath.repmat<T>(array, intDims);
        }

        /// <summary>
        /// Reshape matrix.
        /// </summary>
        /// <typeparam name="T">Matrix element type</typeparam>
        /// <param name="array">Array to reshape</param>
        /// <param name="dims">Dimensions of output array</param>
        /// <returns>Reshaped output array</returns>
        public static ILArray<T> reshape<T>(ILArray<T> array, params int[] dims)
        {
            return ILMath.reshape<T>(array, dims);
        }

        /// <summary>
        /// Reshape matrix.
        /// </summary>
        /// <typeparam name="T">Matrix element type</typeparam>
        /// <param name="array">Array to reshape</param>
        /// <param name="dims">Dimensions of output array</param>
        /// <returns>Reshaped output array</returns>
        public static ILArray<T> reshape<T>(ILArray<T> array, IronPython.Runtime.List dims)
        {
            int[] intDims = new int[dims.Count];
            for (int i = 0; i < intDims.Length; i++) intDims[i] = (int)dims[i];
            return ILMath.reshape<T>(array, intDims);
        }

        /// <summary>
        /// Shifts dimensions of real array.
        /// </summary>
        /// <param name="array">Array to shift dimensions of</param>
        /// <param name="n">Number of places to shift dimensions</param>
        /// <returns>Shifted array</returns>
        public static ILArray<double> shiftdim(ILArray<double> array, int n)
        {
            return (ILArray<double>)array.ShiftDimensions(n);
        }

        /// <summary>
        /// Shifts dimensions of complex array.
        /// </summary>
        /// <param name="array">Array to shift dimensions of</param>
        /// <param name="n">Number of places to shift dimensions</param>
        /// <returns>Shifted array</returns>
        public static ILArray<complex> shiftdim(ILArray<complex> array, int n)
        {
            return (ILArray<complex>)array.ShiftDimensions(n);
        }
    }
}